package com.fhyc.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.OSSObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fhyc.common.XPage;
import com.fhyc.dao.*;
import com.fhyc.dto.*;
import com.fhyc.entity.*;
import com.fhyc.enums.DataTypeEnum;
import com.fhyc.service.OperationalMonitorService;
import com.fhyc.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 胡辰昊
 * @date 2024/7/17
 */
@Service
@Slf4j
public class OperationalMonitorServiceImpl implements OperationalMonitorService {

    public static String MX_URL;

    @Value("${file.python_MX}")
    private void setMxUrl(String url) {
        MX_URL = url;
    }

    public static String BucketName;

    @Value("${oss.aliyun.bucketName}")
    private void setBucketName(String bucketName) {
        BucketName = bucketName;
    }

    public static String AccessKeyId;

    @Value("${oss.aliyun.accessKeyId}")
    private void setAccessKeyId(String accessKeyId) {
        AccessKeyId = accessKeyId;
    }

    public static String End_Point_URL;

    @Value("${oss.aliyun.endpoint}")
    private void setEndPoint(String end_Point_URL) {
        End_Point_URL = end_Point_URL;
    }

    public static String AccessKeySecret;

    @Value("${oss.aliyun.accessKeySecret}")
    private void setAccessKeySecret(String accessKeySecret) {
        AccessKeySecret = accessKeySecret;
    }

    public static String Download_Id;

    @Value("${platform.download_id}")
    private void setDownloadId(String downloadId) {
        Download_Id = downloadId;
    }

    public static String Get_Id_Url;

    @Value("${platform.get_id_url}")
    private void setGetIdUrl(String getIdUrl) {
        Get_Id_Url = getIdUrl;
    }

    public static String Get_File_Url;

    @Value("${platform.get_file_url}")
    private void setGetFileUrl(String getFileUrl) {
        Get_File_Url = getFileUrl;
    }

    //@Resource
    //private SoInBusLoadAccuracyStatsEntityMapper soInBusLoadAccuracyStatsEntityMapper;

    @Resource
    private SoInBusBarLoadAccuracyDao soInBusBarLoadAccuracyDao;

    @Resource
    private SoInBusbarMapper soInBusbarMapper;

    //@Resource
    //private SoInSubstationDao soInSubstationDao;

    @Resource
    private SoInBusLoadInfoDao soInBusLoadInfoDao;

    @Resource
    private SoInLfForecast96lcDao soInLfForecast96LcDao;

    @Resource
    private SoInLfHis96lcDao soInLfHis96LcDao;

    @Resource
    private SoInBaseBusDao soInBaseBusDao;

    @Resource
    private SoInBaseSubstationDao soInBaseSubstationDao;

    @Resource
    private SoInLfForecast96lcDao soInLfForecast96lcDao;

    @Override
    public LoadCurveShowDto queryPredictedAndActualBusValues(LocalDate currentDate, String busId, Integer teamId) throws IOException, ParseException {
        //演示效果 先下载csv文件，发送模型预测，再查询曲线
        //第一步，调用平台接口获取文件id
        RestTemplate restTemplate = new RestTemplate();
        RestTemplate restTemplate2 = new RestTemplate();
        String url = Get_Id_Url + "?datasetId=" + Download_Id;
        log.info("请求获取id地址:{}", url);
        String idResult = restTemplate.getForObject(url, String.class);
        log.info("返回id结果:{}", idResult);
        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonNode = mapper.readTree(idResult);
        JsonNode dataArray = jsonNode.path("data");
        List<String> ids = new ArrayList<>();
        for (JsonNode node : dataArray) {
            ids.add(node.path("id").asText());
        }
        log.info("获取目录下所有id:{}", ids);
        //第二步，获取文件列表
        // 用于存放所有 id 最新的一条记录
        List<JSONObject> allLatest = new ArrayList<>();
        //封装请求
        for (String id : ids) {
            // 构造请求体
            Map<String, Object> body = new HashMap<>();
            body.put("dataSetId", Download_Id);
            body.put("pageNo", 1);
            body.put("pageSize", 20);
            body.put("pointCloudCode", "");
            body.put("type", "0");
            body.put("uploadEndTime", "");
            body.put("uploadStartTime", "");
            body.put("version", id);
            // 设置请求头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            log.info("文件列表 请求参数:{}", headers);
            // 创建HttpEntity,发送POST请求
            HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(body, headers);
            String fileResult = restTemplate2.postForObject(Get_File_Url, requestEntity, String.class);
            JSONObject json2 = JSON.parseObject(fileResult);

            // 取 list
            JSONArray list = json2.getJSONObject("data").getJSONArray("list");
            if (list == null || list.isEmpty()) {
                continue;
            }

            // 按 createTime 降序排序
            list.sort((o1, o2) -> {
                String t1 = ((JSONObject) o1).getString("createTime");
                String t2 = ((JSONObject) o2).getString("createTime");
                return t2.compareTo(t1); // 降序
            });

            // 每个 id 的最新一条
            JSONObject latest = list.getJSONObject(0);
            allLatest.add(latest);
        }
        log.info("文件列表:{}", allLatest);
        // 循环结束后，从 allLatest 里选全局最新的
        String objectName = "";
        if (!allLatest.isEmpty()) {
            allLatest.sort((o1, o2) -> {
                String t1 = o1.getString("createTime");
                String t2 = o2.getString("createTime");
                return t2.compareTo(t1); // 降序
            });

            JSONObject globalLatest = allLatest.get(0);
            objectName = globalLatest.getString("objectName");
            //log.info("最新的一个objectName:{}", objectName);
        }

        //oss下载 文件
        log.info("-----------------开始下载文件:{}----------------", objectName);
        OSS ossClient = new OSSClientBuilder().build(End_Point_URL, AccessKeyId, AccessKeySecret);
        OSSObject ossObject = ossClient.getObject(BucketName, objectName);
        InputStream inputStream = ossObject.getObjectContent();
        //处理流
        //BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        // --- 3. 读取 MXFH 数据，按 busid 分组 ---
        //Map<Integer, ObjectNode> busidToFhMap = new HashMap<>();
        //try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
        //String mxfhPath = "G://mxfh.csv";
        String jsonString = "";
        //Reader reader = new FileReader(mxfhPath);
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT.withFirstRecordAsHeader());
            // 用 Map 分组 busid + ymd
            Map<String, Map<String, Double>> groupedData = new HashMap<>();

            for (CSVRecord record : parser) {
                JSONObject json = new JSONObject();
                int busid = Integer.parseInt(record.get("BUSID"));
                log.info("busid: {}", busid);
                json.put("busid", busid);

                JSONObject content = new JSONObject();
                JSONObject fh = new JSONObject();

                String ymd = record.get("YMD"); // 例如 "20240401"
                log.info("ymd: {}", ymd);
                //插入实际母线数据
                String key = busid + "_" + ymd;

                Map<String, Double> timeValue = groupedData.computeIfAbsent(key, k -> new HashMap<>());

                for (int i = 0; i < 96; i++) {
                    String timeField = String.format("T%04d", i * 15); // T0000~T2345
                    if (record.isMapped(timeField) && record.get(timeField) != null && !record.get(timeField).isEmpty()) {
                        double value = Double.parseDouble(record.get(timeField));
                        timeValue.put(timeField, value);
                    }
                }
                for (String SJKey : groupedData.keySet()) {
                    String[] parts = key.split("_");
                    int busid2 = Integer.parseInt(parts[0]);
                    String ymd2 = parts[1];

                    Map<String, Double> timeValue2 = groupedData.get(key);

                    SoInLfHis96lc soInLfHis96lc = new SoInLfHis96lc();
                    LocalDateTime now = LocalDateTime.now();
                    //soInLfForecast96lc.setSjrq(Timestamp.valueOf(now));
                    soInLfHis96lc.setBusid(busid2);
                    soInLfHis96lc.setYmd(ymd2);

                    // 利用反射设置 T0000~T2345
                    for (int i = 0; i < 96; i++) {
                        String timeField = String.format("T%04d", i * 15);
                        if (timeValue2.containsKey(timeField)) {
                            Double val = timeValue2.get(timeField);
                            try {
                                Method setter = SoInLfHis96lc.class.getMethod("set" + timeField, Double.class);
                                setter.invoke(soInLfHis96lc, val);
                            } catch (NoSuchMethodException e) {
                                // 如果对象没有对应字段，可以选择忽略或者打印日志
                                System.out.println("No setter for " + timeField);
                            }
                        }
                    }

                    // 先删除再插入
                    soInLfHis96LcDao.delete(new QueryWrapper<SoInLfHis96lc>()
                            .eq("busid", busid)
                            .eq("ymd", ymd));

                    soInLfHis96LcDao.insert(soInLfHis96lc);
                    //System.out.println("Inserted SoInLfForecast96lc for busid=" + busid + ", ymd=" + ymd);
                }


                // 遍历所有时间字段
                for (String header : parser.getHeaderMap().keySet()) {
                    if (header.startsWith("T")) {
                        // String hhmm = header.substring(1);
                        // long ts = parseDateToTimestamp2(dateStr);
                        // 去掉 T
                        String hhmm = header.substring(1);
                        String dateStr = formatYmdTime(ymd, hhmm);
                        int hour = Integer.parseInt(hhmm.substring(0, 2));
                        int minute = Integer.parseInt(hhmm.substring(2, 4));

                        // 解析日期
                        LocalDate date = LocalDate.parse(ymd, DateTimeFormatter.ofPattern("yyyyMMdd"));

                        // 特殊处理 T2400 → 第二天 00:00
                        if (hour == 24) {
                            date = date.plusDays(1);
                            hour = 0;
                        }

                        LocalDateTime ldt = LocalDateTime.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth(), hour, minute);

                        // 转成毫秒时间戳（北京时间 +8）
                        long ts = ldt.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();

                        double value = Double.parseDouble(record.get(header));

                        //log.info("header={}, dateStr={}, ts={}, value={}", header, dateStr, ts, value);

                        fh.put(String.valueOf(ts), value);
                    }
                }
                log.info("fh.size={}", fh.size());

                content.put("fh", fh);
                json.put("content", content);
                JSONObject result = new JSONObject();
                result.put("input_data", json);
                //log.info("MXFH: {}", json);
                jsonString = result.toJSONString();
            }


            //  try (BufferedReader reader2 = Files.newBufferedReader(Paths.get(mxfhPath))) {
/*            reader.readLine();
            if (reader.readLine() == null) log.error("MXFH 文件为空");
            while ((line=reader.readLine())!=null) {
                String[] data = line.split(",");

                // 解析BUSID
                String busid = data[0];
                log.info("busid: {}", busid);
            }*/

            //String[] rawHeaders = headerLine.split(",", -1);
            //String[] cleanHeaders = Arrays.stream(rawHeaders).map(String::trim).map(h -> h.isEmpty() ? "null" : h).toArray(String[]::new);

            //CSVFormat format = CSVFormat.DEFAULT.withHeader(cleanHeaders).withSkipHeaderRecord().withIgnoreHeaderCase().withTrim();
/*            CSVFormat format = CSVFormat.DEFAULT
                    .withFirstRecordAsHeader()  // 使用第一行作为 header
                    .withIgnoreHeaderCase()     // 忽略大小写
                    .withTrim();

            CSVParser parser = new CSVParser(reader, format);
            Map<String, Integer> headerMap = parser.getHeaderMap();*/
            //log.info(headerMap.toString());

/*            String busidHeader = parser.getHeaderMap().keySet().stream()
                    .map(String::trim)
                    .filter(h -> h.equalsIgnoreCase("BUSID"))
                    .findFirst()
                    .orElseThrow(() -> new IllegalStateException("缺少 BUSID 列"));*/
            //log.info("busidHeader: {}", busidHeader);

/*            for (CSVRecord rec : parser) {
                // 取 busid
                //String busidStr = rec.get(busidHeader);  // 得到字符串
                //int busid = Integer.parseInt(busidStr);    // 转成整数
                String busid = rec.get("BUSID");
                String ymd = rec.get("YMD");            // 其他列示例
                log.info("busid: {}", busid);
                log.info("ymd: {}", ymd);*/

               /* int busid = Integer.parseInt(rec.get(busidHeader));
                String ymd = rec.get("YMD");
                log.info("busid:{}", busid);
                log.info("ymd:{}", ymd);
                ObjectNode fhNode = busidToFhMap.computeIfAbsent(busid, k -> mapper.createObjectNode());
                log.info("fhNode:{}", fhNode);*/

/*                for (String header : headerMap.keySet()) {
                    if (!header.startsWith("T")) continue;
                    String hhmm = header.substring(1);
                    String dateStr = formatYmdTime(ymd, hhmm);
                    long ts = parseDateToTimestamp2(dateStr);
                    double val = parseDoubleOrZero(rec.get(header));
                    fhNode.put(String.valueOf(ts), val);
                }*/
            //  }
        } catch (Exception e) {
            log.error("解析 MXFH 文件失败:{}", e.getMessage());
        }

        // --- 4. 合成每个 busid 的 JSON ---
/*        List<String> results = new ArrayList<>();
        for (Map.Entry<Integer, ObjectNode> entry : busidToFhMap.entrySet()) {
            int busid = entry.getKey();
            ObjectNode fhData = entry.getValue();

            ObjectNode root = mapper.createObjectNode();
            ObjectNode inputData = root.putObject("input_data");
            inputData.put("busid", busid);

            ObjectNode content = inputData.putObject("content");
            content.set("fh", fhData);

            results.add(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root));
        }
        log.info("csv实际母线负荷文件封装结果，{}", results);*/


        //inputStream.close();
        //ossClient.shutdown();

        //调用模型
        try {
            int busid = 0;
            try {
                ObjectMapper object = new ObjectMapper();
                JsonNode rootNode = object.readTree(String.valueOf(jsonString));
                log.info("json: {}", rootNode);
                busid = rootNode.path("input_data").path("busid").asInt();
                log.info("提取到的 busid: {}", busid);
            } catch (IOException e) {
                log.error("busid解析失败:{}", e.getMessage());
            }
            // 发送 POST 请求
            RestTemplate restTemplate3 = new RestTemplate();
            // 设置请求头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            // 创建HttpEntity,发送POST请求
            HttpEntity<String> requestEntity = new HttpEntity<>(String.valueOf(jsonString), headers);

            log.info("--------------开始请求母线模型接口------------");
            ResponseEntity<String> response = restTemplate3.postForEntity(MX_URL, requestEntity, String.class);
            // 序列化整个response为JSON字符串
            ObjectMapper objectMapper = new ObjectMapper();

            log.info("--------------开始解析母线模型接口返回数据------------");
            //String responseBody = objectMapper2.writeValueAsString(response);
            //log.info(responseBody);
            String body = response.getBody();
            //log.info("body:{}", body);

            // 获取原始 data 字段的字符串值
            log.info("-------------- data 字段的字符串值------------");
            JsonNode root = objectMapper.readTree(body);
            String dataStr = root.path("data").asText();
            //log.info(" data 字段的字符串值: {}", dataStr);

            // 转换target
            log.info("-------------- target 字段的字符串值------------");
            JsonNode dataNode = objectMapper.readTree(dataStr);
            JsonNode targetNode = dataNode.path("target");
            //log.info("target 字段的字符串值:{}", targetNode.asText());

            /*//测试
            String json = "{\"target\": {\"freq\": \"15T\", \"data\": {\"fh\": {\"2025-06-22 00:00:00\": 50.22, \"2025-06-22 00:15:00\": 51.0}}}}";
*/

            // 获取数据部分
            if (targetNode.isTextual()) {
                targetNode = objectMapper.readTree(targetNode.asText());
            }
            JsonNode fhNode = targetNode.path("data").path("fh");
            log.info("输出fh数据:{}", fhNode); // 输出fh数据

            // 提取 fh 数据，保存
            Iterator<Map.Entry<String, JsonNode>> fields = fhNode.fields();

            SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            SimpleDateFormat timeFormat = new SimpleDateFormat("HHmm");
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");

            Map<Integer, Map<String, Double>> dateToTimeValueMap = new HashMap<>();

            while (fields.hasNext()) {
                //添加删除逻辑
                Map.Entry<String, JsonNode> entry = fields.next();
                String timestamp = entry.getKey();   // 时间戳
                Double value = entry.getValue().asDouble();  // 数值
                // 将时间戳字符串转为 Date 对象
                Date date = inputFormat.parse(timestamp);
                // 提取小时和分钟，格式化为 "HHmm"
                String timeField = timeFormat.format(date);
                String dateStr = dateFormat.format(date);
                String fieldName = "T" + timeField;

                Integer ymd = Integer.valueOf(dateStr);
                //根据日期分组
                dateToTimeValueMap.putIfAbsent(ymd, new HashMap<>());
                //储存时间点和值
                dateToTimeValueMap.get(ymd).put(fieldName, value);
            }
            //处理每一天数据
            for (Map.Entry<Integer, Map<String, Double>> entry : dateToTimeValueMap.entrySet()) {
                Integer ymd = entry.getKey();
                Map<String, Double> timeValue = entry.getValue();

                // 提取并解析fh数据
                //SoInBusLoadForecast busLoadForecast = new SoInBusLoadForecast();
                SoInLfForecast96lc soInLfForecast96lc = new SoInLfForecast96lc();
                // 获取当前时间
                LocalDateTime now = LocalDateTime.now();
                soInLfForecast96lc.setSjrq(Timestamp.valueOf(now));
                soInLfForecast96lc.setBusid(busid);
                soInLfForecast96lc.setYmd(ymd);

                for (int i = 0; i < 96; i++) {
                    // 生成时间点的字符串，例如 T0000, T0015, ..., T2345
                    String timeField = String.format("T%04d", i * 15);  // 格式化为 T0000, T0015, ..., T2345

                    // 判断时间字段是否在 Map 中存在并且值不为 null
                    if (timeValue.containsKey(timeField) && timeValue.get(timeField) != null) {
                        // 获取对应的值并设置到 forecast 对象的相应字段
                        soInLfForecast96lc.getClass().getMethod("set" + timeField, timeValue.get(timeField).getClass())
                                .invoke(soInLfForecast96lc, timeValue.get(timeField));
                    }
                }
                log.info("-----------------------插入数据----------------------");
                log.info(soInLfForecast96lc.toString());
                //先删除
                log.info("-----------------删除数据库------------");
                soInLfForecast96lcDao.delete(new QueryWrapper<SoInLfForecast96lc>().eq("busid", busid).eq("ymd", ymd));
                // 保存到数据库
                log.info("-----------------存数据库------------");
                soInLfForecast96lcDao.insert(soInLfForecast96lc);
            }
        } catch (Exception e) {
            log.error(e.toString());
        }

        //实际：昨日 今日
        //预测母线曲线
        String id = busId.replace("bus_", "");
        LocalDate yesterdayDate = currentDate.minusDays(1);
        LocalDate tomorrowDate = currentDate.plusDays(1);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        // 将LocalDate格式化为字符串
        String yesterday = yesterdayDate.format(formatter);
        String current = currentDate.format(formatter);
        String tomorrow = tomorrowDate.format(formatter);

/*        LambdaQueryWrapper<SoInBusLoadInfoEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SoInBusLoadInfoEntity::getBusid, id)
                .and(i -> i.or().eq(SoInBusLoadInfoEntity::getYmd, yesterday)
                        .or().eq(SoInBusLoadInfoEntity::getYmd, current));
                        //.or().eq(SoInBusLoadInfoEntity::getYmd, tomorrow));
        List<SoInBusLoadInfoEntity> loadInfos = soInBusLoadInfoDao.selectList(wrapper);*/

        LambdaQueryWrapper<SoInLfHis96lc> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SoInLfHis96lc::getBusid, id)
                .and(i -> i.or().eq(SoInLfHis96lc::getYmd, yesterday)
                        .or().eq(SoInLfHis96lc::getYmd, current));
        List<SoInLfHis96lc> loadInfos = soInLfHis96LcDao.selectList(wrapper);

        List<LoadCurveDto> curveList = new ArrayList<>();


        //目前只有一个预测厂商
        List<SoInLfForecast96lc> forecast96lcList;
        if (teamId == 1) {
            LambdaQueryWrapper<SoInLfForecast96lc> wrapper2 = new LambdaQueryWrapper<>();
            wrapper2.eq(SoInLfForecast96lc::getBusid, id)
                    .and(i -> i.or().eq(SoInLfForecast96lc::getYmd, tomorrow));
            forecast96lcList = soInLfForecast96LcDao.selectList(wrapper2);

            //预测-明日
            for (SoInLfForecast96lc loadInfo : forecast96lcList) {
                String ymd = String.valueOf(loadInfo.getYmd());

                DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyyMMdd");

                LocalDate date = LocalDate.parse(ymd, formatter2);

                curveList.add(
                        new LoadCurveDto(switchLocalDate(date, currentDate), DataTypeEnum.FCST_VALUE, loadInfo.getValueList())
                );
            }

        } else {
            forecast96lcList = null;
        }


        //实际-昨日，今日
        for (SoInLfHis96lc loadInfo : loadInfos) {
            String ymd = String.valueOf(loadInfo.getYmd());

            DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyyMMdd");

            LocalDate date = LocalDate.parse(ymd, formatter2);

            curveList.add(
                    new LoadCurveDto(switchLocalDate(date, currentDate), DataTypeEnum.ACTUAL, loadInfo.getValueList())
            );
        }


        //演示当天效果
        // 获取当前时间
        Calendar calendar = Calendar.getInstance();
        int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
        int currentMinute = calendar.get(Calendar.MINUTE);
        // 将小时和分钟转换为总分钟数
        int totalMinutes = currentHour * 60 + currentMinute;
        // 每15分钟一个点，计算总点数
        int totalPoints = (int) Math.ceil((double) totalMinutes / 15);
        //log.info("当天时间点：" + totalPoints);
        //List<LoadCurveDto> curveList
        LocalDate currentNow = LocalDate.now();
        for (LoadCurveDto dto : curveList) {
            if (dto.getName().equals("今日") && currentDate.isAfter(currentNow)) {
                for (int i = 1; i <= 96; i++) {
                    //  List<Double> curve = null;
                    //log.info(curve.toString());
                    //curve.add( null);
                    //log.info(String.valueOf(i));
                    dto.setCurve(null);
                }
            } else if (dto.getName().equals("今日") && currentDate.equals(currentNow)) {
                for (int i = totalPoints; i < 96; i++) {
                    List<Double> curve = dto.getCurve();
                    //log.info(curve.toString());
                    curve.set(i, null);
                    //log.info(String.valueOf(i));
                }
            }
        }


/*        LambdaQueryWrapper<BuBusLoadEntity> lqw = new LambdaQueryWrapper<>();
        lqw.eq(BuBusLoadEntity::getBusId, busId)
                .eq(BuBusLoadEntity::getTeamId, teamId)
                .and(i -> i.or().eq(BuBusLoadEntity::getDataDate, yesterdayDate).eq(BuBusLoadEntity::getDataType, 1)
                        .or().eq(BuBusLoadEntity::getDataDate, currentDate).eq(BuBusLoadEntity::getDataType, 1)
                        .or().eq(BuBusLoadEntity::getDataDate, tomorrowDate).eq(BuBusLoadEntity::getDataType, 2));
        List<BuBusLoadEntity> buBusLoadEntities = buBusLoadMapper.selectList(lqw);

        List<LoadCurveDto> curveList = new ArrayList<>();
        for (BuBusLoadEntity buBusLoadEntity : buBusLoadEntities) {
            curveList.add(
                    new LoadCurveDto(switchLocalDate(buBusLoadEntity.getDataDate(), currentDate), DataTypeEnum.ACTUAL, buBusLoadEntity.getValueList())
            );
        }*/

        LoadCurveShowDto loadCurveShowDto = new LoadCurveShowDto();
        loadCurveShowDto.setCurveList(curveList);

        // 定义 DateTimeFormatter
        DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        // 使用 Stream API 进行转换
        List<String> stringList = getTimeList(currentDate).stream()
                .map(localDateTime -> localDateTime.format(formatter3)) // 格式化 LocalDateTime
                .collect(Collectors.toList()); // 收集结果到 List<String>
        loadCurveShowDto.setDateTime(stringList);


        return loadCurveShowDto;
    }

    @Override
    public XPage<AccuracyIndexDTO> queryAccuracyIndex(Integer pageNum, Integer pageSize, LocalDate dataDate, String busId) throws NoSuchFieldException, IllegalAccessException {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        // 将LocalDate格式化为字符串
        String dataString = dataDate.format(formatter);
        String id = busId.replace("bus_", "");
        List<AccuracyIndexDTO> list = new ArrayList<>();
        AccuracyIndexDTO dto = new AccuracyIndexDTO();
        dto.setBusId(Integer.valueOf(id));

        SoInBaseBus baseBus = soInBaseBusDao.selectOne(new QueryWrapper<SoInBaseBus>().eq("BUSID", Integer.valueOf(id)));
        if (baseBus != null) {
            dto.setBusName(baseBus.getName());
            Integer stationid = baseBus.getStationid();
            SoInBaseSubstation substation = soInBaseSubstationDao.selectOne(new QueryWrapper<SoInBaseSubstation>().eq("STATIONID", stationid));
            if (substation != null) {
                dto.setStationName(substation.getStName());
            }
        }

        QueryWrapper<SoInLfForecast96lc> queryWrapper = new QueryWrapper<>();
        QueryWrapper<SoInLfHis96lc> queryWrapper2 = new QueryWrapper<>();
        SoInLfHis96lc lfHis96lc;
        SoInLfForecast96lc lfForecast96lc;

        queryWrapper.eq("YMD", dataString).eq("BUSID", id);
        List<SoInLfForecast96lc> forecast96lcs = soInLfForecast96LcDao.selectList(queryWrapper);
        queryWrapper2.eq("BUSID", id).eq("YMD", dataString);
        List<SoInLfHis96lc> his96lcs = soInLfHis96LcDao.selectList(queryWrapper2);

        double maxActual = Double.NEGATIVE_INFINITY;
        String maxActualField = null;
        double maxDeviation = Double.NEGATIVE_INFINITY;
        double minDeviation = Double.POSITIVE_INFINITY;
        double avgAccuracy;
        double maxActualValue;
        double maxPredictValue;

        if (forecast96lcs.size() > 0 && his96lcs.size() > 0) {
            lfForecast96lc = forecast96lcs.get(0);
            lfHis96lc = his96lcs.get(0);

            Class<?> cls = SoInLfHis96lc.class;
            Class<?> cls2 = SoInLfForecast96lc.class;
            List<Double> accuracies = new ArrayList<>(96);

            for (int i = 0; i < 96; i++) {
                int totalMinutes = i * 15;
                int hour = totalMinutes / 60;
                int minute = totalMinutes % 60;
                String fieldName = String.format("t%02d%02d", hour, minute);

                Field f = cls.getDeclaredField(fieldName);
                Field f2 = cls2.getDeclaredField(fieldName);
                f.setAccessible(true);
                f2.setAccessible(true);
                //double actual = ((Number) f.get(lfHis96lc)).doubleValue();
                double actual = lfHis96lc != null ? (double) f.get(lfHis96lc) : 0.0;
                double predict = lfForecast96lc != null ? (double) f2.get(lfForecast96lc) : 0.0;
                double deviation = Math.abs(actual - predict);
                maxDeviation = Math.max(maxDeviation, deviation);
                minDeviation = Math.min(minDeviation, deviation);

                if (actual > maxActual) {
                    maxActual = actual;
                    maxActualField = fieldName;
                }

                double pointAcc;
                if (actual == 0) {
                    pointAcc = 0.0;
                } else {
                    pointAcc = (1.0 - deviation / actual) * 100.0;
                }
                accuracies.add(pointAcc);
            }

            double sum = 0;
            for (double acc : accuracies) {
                sum += acc;
            }

            avgAccuracy = sum / accuracies.size();
            BigDecimal bd = new BigDecimal(avgAccuracy);
            bd = bd.setScale(2, RoundingMode.HALF_UP); // 四舍五入
            avgAccuracy = bd.doubleValue();
            dto.setAvgAccuracy(avgAccuracy);

            Field maxField = cls.getDeclaredField(maxActualField);
            maxField.setAccessible(true);

            Field maxField2 = cls2.getDeclaredField(maxActualField);
            maxField2.setAccessible(true);

            maxActualValue = (double) maxField.get(lfHis96lc);
            maxPredictValue = (double) maxField2.get(lfForecast96lc);


            double maxPointAcc;
            if (maxActualValue == 0) {
                maxPointAcc = 0.0;
            } else {
                maxPointAcc = (1.0 - Math.abs(maxActualValue - maxPredictValue) / maxActualValue) * 100.0;
            }

            BigDecimal maxPoint = new BigDecimal(maxPointAcc);
            maxPoint = maxPoint.setScale(2, RoundingMode.HALF_UP); // 四舍五入
            maxPointAcc = maxPoint.doubleValue();
            dto.setMaxLoadAccuracy(maxPointAcc);

            BigDecimal min = new BigDecimal(minDeviation);
            min = min.setScale(2, RoundingMode.HALF_UP); // 四舍五入
            minDeviation = min.doubleValue();
            dto.setMinForecastDeviation(minDeviation);

            BigDecimal max = new BigDecimal(maxDeviation);
            max = max.setScale(2, RoundingMode.HALF_UP); // 四舍五入
            maxDeviation = max.doubleValue();
            dto.setMaxForecastDeviation(maxDeviation);
        } else {
            dto.setAvgAccuracy(0.0);
            dto.setMaxLoadAccuracy(0.0);
            dto.setMinForecastDeviation(0.0);
            dto.setMaxForecastDeviation(0.0);
        }

        //更新准确率表
        /*accuracy.setAccuracy(avgAccuracy);
        accuracy.setMaxForecastDeviation(maxDeviation);
        accuracy.setMinForecastDeviation(minDeviation);
        accuracy.setMaxLoadAccuracy(maxPointAcc);
        accuracy.setCreateTime(new Date());
        soInBusBarLoadAccuracyDao.updateById(accuracy);*/

        list.add(dto);

        // }
/*        result.setRecords(list);
        result.setTotal(dtoxPage.getTotal());
        result.setSize(dtoxPage.getSize());
        result.setPages(dtoxPage.getPages());
        return result;*/

        // 手动分页逻辑
        int total = list.size();
        int fromIndex = (pageNum - 1) * pageSize;
        int toIndex = Math.min(fromIndex + pageSize, total);

        List<AccuracyIndexDTO> pagedList = Collections.emptyList();
        if (fromIndex <= total && fromIndex < toIndex) {
            pagedList = list.subList(fromIndex, toIndex);
        }
        XPage<AccuracyIndexDTO> info = new XPage<>();
        info.setRecords(pagedList);
        info.setPages(pageNum);
        info.setSize(pageSize);
        info.setTotal(list.size());
        return info;

/*        } else {
            XPage<AccuracyIndexDTO> result = new XPage<>();
            List<AccuracyIndexDTO> list = new ArrayList<>();
            XPage<SoInBusBarLoadAccuracy> accuracyIndexDTOXPage = soInBusBarLoadAccuracyDao.queryAccuracyIndex(page, dataDate, id);
            List<SoInBusBarLoadAccuracy> records = accuracyIndexDTOXPage.getRecords();
            for (SoInBusBarLoadAccuracy accuracy : records) {
                AccuracyIndexDTO dto = new AccuracyIndexDTO();
                dto.setAvgAccuracy(null);
                dto.setMaxLoadAccuracy(null);
                dto.setMaxForecastDeviation(null);
                dto.setMinForecastDeviation(null);
                dto.setBusId(Integer.valueOf(id));
                dto.setBusName(accuracy.getName());
                dto.setStationName(accuracy.getStName());
                list.add(dto);
            }
            return result;
        }*/
    }

    @Override
    public void AccuracyIndexExportExcel(HttpServletResponse response, LocalDate localDate, String busId) throws IOException, NoSuchFieldException, IllegalAccessException {

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        // 将LocalDate格式化为字符串
        String dataString = localDate.format(formatter);

        String id = busId.replace("bus_", "");
        AccuracyIndexDTO dto = new AccuracyIndexDTO();
        dto.setBusId(Integer.valueOf(id));

        SoInBaseBus baseBus = soInBaseBusDao.selectOne(new QueryWrapper<SoInBaseBus>().eq("BUSID", Integer.valueOf(id)));
        if (baseBus != null) {
            dto.setBusName(baseBus.getName());
            Integer stationid = baseBus.getStationid();
            SoInBaseSubstation substation = soInBaseSubstationDao.selectOne(new QueryWrapper<SoInBaseSubstation>().eq("STATIONID", stationid));
            if (substation != null) {
                dto.setStationName(substation.getStName());
            }
        }

        QueryWrapper<SoInLfForecast96lc> queryWrapper = new QueryWrapper<>();
        QueryWrapper<SoInLfHis96lc> queryWrapper2 = new QueryWrapper<>();
        SoInLfHis96lc lfHis96lc = null;
        SoInLfForecast96lc lfForecast96lc = null;

        queryWrapper.eq("YMD", dataString).eq("BUSID", id);
        List<SoInLfForecast96lc> forecast96lcs = soInLfForecast96LcDao.selectList(queryWrapper);


        queryWrapper2.eq("BUSID", id).eq("YMD", dataString);
        List<SoInLfHis96lc> his96lcs = soInLfHis96LcDao.selectList(queryWrapper2);

        double maxActual = Double.NEGATIVE_INFINITY;
        String maxActualField = null;
        double maxDeviation = Double.NEGATIVE_INFINITY;
        double minDeviation = Double.POSITIVE_INFINITY;
        double avgAccuracy;
        double maxActualValue;
        double maxPredictValue;

        if (forecast96lcs.size() > 0 && his96lcs.size() > 0) {
            lfForecast96lc = forecast96lcs.get(0);
            lfHis96lc = his96lcs.get(0);

            Class<?> cls = SoInLfHis96lc.class;
            Class<?> cls2 = SoInLfForecast96lc.class;
            List<Double> accuracies = new ArrayList<>(96);

            for (int i = 0; i < 96; i++) {
                int totalMinutes = i * 15;
                int hour = totalMinutes / 60;
                int minute = totalMinutes % 60;
                String fieldName = String.format("t%02d%02d", hour, minute);

                Field f = cls.getDeclaredField(fieldName);
                Field f2 = cls2.getDeclaredField(fieldName);
                f.setAccessible(true);
                f2.setAccessible(true);
                //double actual = ((Number) f.get(lfHis96lc)).doubleValue();
                double actual = lfHis96lc != null ? (double) f.get(lfHis96lc) : 0.0;
                double predict = lfForecast96lc != null ? (double) f2.get(lfForecast96lc) : 0.0;
                double deviation = Math.abs(actual - predict);
                maxDeviation = Math.max(maxDeviation, deviation);
                minDeviation = Math.min(minDeviation, deviation);

                if (actual > maxActual) {
                    maxActual = actual;
                    maxActualField = fieldName;
                }

                double pointAcc;
                if (actual == 0) {
                    pointAcc = 0.0;
                } else {
                    pointAcc = (1.0 - deviation / actual) * 100.0;
                }
                accuracies.add(pointAcc);
            }

            double sum = 0;
            for (double acc : accuracies) {
                sum += acc;
            }
            avgAccuracy = sum / accuracies.size();
            BigDecimal bd = new BigDecimal(avgAccuracy);
            bd = bd.setScale(2, RoundingMode.HALF_UP); // 四舍五入
            avgAccuracy = bd.doubleValue();

            dto.setAvgAccuracy(avgAccuracy);

            Field maxField = cls.getDeclaredField(maxActualField);
            maxField.setAccessible(true);

            Field maxField2 = cls2.getDeclaredField(maxActualField);
            maxField2.setAccessible(true);

            maxActualValue = lfHis96lc != null ? (double) maxField.get(lfHis96lc) : 0.0;
            maxPredictValue = lfHis96lc != null ? (double) maxField2.get(lfForecast96lc) : 0.0;
            double maxPointAcc;
            if (maxActualValue == 0) {
                maxPointAcc = 0.0;
            } else {
                maxPointAcc = (1.0 - Math.abs(maxActualValue - maxPredictValue) / maxActualValue) * 100.0;
            }

            BigDecimal maxPoint = new BigDecimal(maxPointAcc);
            maxPoint = maxPoint.setScale(2, RoundingMode.HALF_UP); // 四舍五入
            maxPointAcc = maxPoint.doubleValue();
            dto.setMaxLoadAccuracy(maxPointAcc);

            BigDecimal min = new BigDecimal(minDeviation);
            min = min.setScale(2, RoundingMode.HALF_UP); // 四舍五入
            minDeviation = min.doubleValue();
            dto.setMinForecastDeviation(minDeviation);

            BigDecimal max = new BigDecimal(maxDeviation);
            max = max.setScale(2, RoundingMode.HALF_UP); // 四舍五入
            maxDeviation = max.doubleValue();
            dto.setMaxForecastDeviation(maxDeviation);
        } else {
            dto.setAvgAccuracy(0.0);
            dto.setMaxLoadAccuracy(0.0);
            dto.setMinForecastDeviation(0.0);
            dto.setMaxForecastDeviation(0.0);
        }

        AccuracyDTO accuracyDTO = new AccuracyDTO();
        //loadAccuracyList.add(dto);
        accuracyDTO.setStName(dto.getStationName());
        accuracyDTO.setName(dto.getBusName());
        accuracyDTO.setAccuracy(dto.getAvgAccuracy());
        accuracyDTO.setMaxLoadAccuracy(dto.getMaxLoadAccuracy());
        accuracyDTO.setMinForecastDeviation(dto.getMinForecastDeviation());
        accuracyDTO.setMaxForecastDeviation(dto.getMaxForecastDeviation());


        List<AccuracyDTO> result = new ArrayList<>();
        result.add(accuracyDTO);


        ServletOutputStream outputStream = response.getOutputStream();

        try {
            String fileName = UUID.randomUUID().toString();
            ///FileUtil.setExcelResponseProp(response, "准确率指标结果");
            FileUtil.setExcelResponseProp(response, fileName);
            EasyExcel.write(outputStream, AccuracyDTO.class)//对应的导出实体类
                    .excelType(ExcelTypeEnum.XLSX)//excel文件类型，包括CSV、XLS、XLSX
                    .sheet("准确率指标")//导出sheet页名称
                    .doWrite(result); //查询获取的数据集合List<T>，转成excel
        } catch (
                UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } finally {
            outputStream.flush();
            outputStream.close();
        }
    }

    // 获取时间数组
    private List<LocalDateTime> getTimeList(LocalDate date) {
        List<LocalDateTime> timeList = new ArrayList<>();
        LocalDateTime start = date.atStartOfDay();
        for (LocalDateTime i = start; i.isBefore(start.plusDays(1)); i = i.plusMinutes(15)) {
            timeList.add(i);
        }
        return timeList;
    }

    // 日期名称转换
    private String switchLocalDate(LocalDate localDate, LocalDate currentDate) {
        if (localDate.equals(currentDate.minusDays(1))) {
            return "昨日";
        } else if (localDate.equals(currentDate)) {
            return "今日";
        } else {
            return "预测";
        }
    }


    public static class PredictionResult {
        private double maxDiff;
        private double minDiff;

        public PredictionResult(double maxDiff, double minDiff) {
            this.maxDiff = maxDiff;
            this.minDiff = minDiff;
        }

        public double getMaxDiff() {
            return maxDiff;
        }

        public double getMinDiff() {
            return minDiff;
        }

    }

    public static PredictionResult calculatePredictionDifference(List<Double> values) {
        double globalMaxDiff = Double.NEGATIVE_INFINITY;
        double globalMinDiff = Double.POSITIVE_INFINITY;


        //List<Double> values = entity.getTimePoints();

        double maxDiff = Double.NEGATIVE_INFINITY;
        double minDiff = Double.POSITIVE_INFINITY;

        // Calculate the difference between adjacent values
        for (int i = 0; i < values.size() - 1; i++) {
            double diff = Math.abs(values.get(i + 1) - values.get(i));
            maxDiff = Math.max(maxDiff, diff);
            minDiff = Math.min(minDiff, diff);
        }

        // Update global max and min differences
        globalMaxDiff = Math.max(globalMaxDiff, maxDiff);
        globalMinDiff = Math.min(globalMinDiff, minDiff);


        return new PredictionResult(globalMaxDiff, globalMinDiff);
    }

    /**
     * 解析字符串为 double，不可用时返回 0
     */
    private double parseDoubleOrZero(String s) {
        if (StringUtils.isBlank(s)) return 0.0;
        try {
            return Double.parseDouble(s);
        } catch (NumberFormatException e) {
            return 0.0;
        }
    }

    /**
     * 将 YMD（如 "20100508"）和 HHmm （如 "1130"）组合成 "yyyy/MM/dd HH:mm"。
     *
     * @param ymd  八位日期字符串
     * @param hhmm 四位时间字符串
     */
    private String formatYmdTime(String ymd, String hhmm) {
        // 假设 ymd 长度=8, hhmm 长度=4
        return ymd.substring(0, 4) + "/" +
                ymd.substring(4, 6) + "/" +
                ymd.substring(6, 8) + " " +
                hhmm.substring(0, 2) + ":" +
                hhmm.substring(2, 4);
    }

    // 解析日期字符串为时间戳
    public static long parseDateToTimestamp(String dateStr) throws ParseException {
        if (dateStr.contains("-")) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            Date date = dateFormat.parse(dateStr);
            return date.getTime();
        } else {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm");
            Date date = dateFormat.parse(dateStr);
            return date.getTime();
        }
    }

    public static long parseDateToTimestamp2(String dateStr) throws ParseException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
        Date date = dateFormat.parse(dateStr);
        return date.getTime();
    }
}
